//
//  NetWorkManger.swift
//  MGWenXue
//
//  Created by MGDeveloper on 2021/5/9.
//  Copyright © 2021 MGWX. All rights reserved.
//

import Alamofire
import Moya
import UIKit
import CoreTelephony
import SystemConfiguration

public typealias responCallBack = (_ response:ResponseData) -> Void

private let networkReachabilityManager = NetworkReachabilityManager.default

private let AlamoManager: Alamofire.Session = {
    let config = URLSessionConfiguration.default
    config.timeoutIntervalForRequest = 20
    return Alamofire.Session(configuration: config)
}()

private let NetworkProvider = MoyaProvider<SZRequest>(session: AlamoManager)

private enum SZRequest {
    case request(RequestProtocol)
}

extension SZRequest: TargetType {
    
    var pare:RequestProtocol {
        switch self {
        case .request(let pare):
            return pare
        }
    }
    
    var baseURL: URL { URL(string: pare.host)!}
    var path: String { pare.path }
    var method: Moya.Method { pare.method }
    var sampleData: Data { Data()}
    var task: Task { pare.task}
    var headers: [String : String]? { [String:String]().merging(pare.pubicCommonHeaders).merging(pare.headers) }
}

public class NetWorkManger: NSObject {
    
    public static var share = NetWorkManger()
    public var statusModelhandle:((_ pare:RequestProtocol,_ response:ResponseData) -> Void)?
    
    public static var kWindow: UIWindow?
    
    @discardableResult
    public class func request<T: RequestProtocol>(pare: T,
                                                 loadIngView: UIView? = nil,
                                                 isRefresh: Bool = false,
                                                 progress: ProgressBlock? = .none,
                                                 callback: responCallBack? = nil
        ) -> Cancellable? {
        // 记录初始加载状态，仅在首次请求时显示加载视图
        let shouldShowLoading = isRefresh == false && pare.retryCount == 0
        if shouldShowLoading {
            loadIngView?.toastActivity()
        }
        
        let loadStartDate = Date()
        
        // 定义递归请求函数，跟踪当前重试次数
        func performRequest(currentRetryCount: Int) -> Cancellable? {
            return NetworkProvider.request(SZRequest.request(pare), progress: progress) { [weak loadIngView] result in
                // 所有请求完成（包括最后一次重试）后隐藏加载视图
                if currentRetryCount == pare.retryCount {
                    if isRefresh == false {
                        loadIngView?.hideActivity()
                    }
                }
                
                var isFromCache = false
                var resultData = result
                var shouldRetry = false
                
                switch result {
                case .success(let successData):
                    // 成功处理：解析数据并缓存
                    do {
                        if var json = try JSONSerialization.jsonObject(with: successData.data, options: []) as? [String: Any] {
                            if let base64String = json["data"] as? String,
                               let decodedData = Data(base64Encoded: base64String),
                               let jsonObject = try? JSONSerialization.jsonObject(with: decodedData, options: []) {
                                json["data"] = jsonObject
                                let updatedData = try JSONSerialization.data(withJSONObject: json, options: [])
                                resultData = .success(Moya.Response(statusCode: successData.statusCode, data: updatedData))
                            }
                        }
                    } catch {
                        print("Failed to decode and reconstruct JSON: \(error)")
                    }
                    
                    pare.cache.save(data: successData.data)
                    
                case .failure(let error):
                    // 失败处理：判断是否需要重试
                    if currentRetryCount < pare.retryCount {
                        // 还有重试次数，标记需要重试
                        shouldRetry = true
                    } else {
                        // 重试次数耗尽，尝试使用缓存
                        if let data = pare.cache.value {
                            resultData = .success(Moya.Response(statusCode: 200, data: data))
                            isFromCache = true
                        } else if pare.hideToast != true {
                            // 无缓存时显示错误提示
                            if case .underlying(let err, _) = error,
                               let underlyingError = err.asAFError?.underlyingError as? NSError,
                               (underlyingError.code == CFNetworkErrors.cfurlErrorNotConnectedToInternet.rawValue ||
                                underlyingError.code == CFNetworkErrors.cfurlErrorNetworkConnectionLost.rawValue) {
                                kWindow?.toast("Network error, please refresh and try again")
                            } else {
                                kWindow?.toast("Error, please refresh and try again")
                            }
                        }
                    }
                }
                
                // 判断是否需要重试
                if shouldRetry {
                    // 递归调用进行重试
                    _ = performRequest(currentRetryCount: currentRetryCount + 1)
                } else {
                    // 所有重试完成，处理最终结果
                    let data = ResponseData(result: resultData, fromCache: isFromCache, pare: pare, startDate: loadStartDate)
                    
                    if case .success(_) = resultData,
                       let statusModel: ResponseModel<KJSONNull> = data.getModel() {
                        if pare.hideToast != true,
                           statusModel.responseType != .OK,
                           let msg = statusModel.msg,
                           msg.length > 0,
                           !ResponseType.noAutoToastType.contains(statusModel.responseType) {
                            loadIngView?.toast(msg)
                        }
                    }
                    
                    Self.share.statusModelhandle?(pare, data)
                    callback?(data)
                }
            }
        }
        
        // 启动首次请求，初始重试次数为0
        return performRequest(currentRetryCount: 0)
    }
}

extension NetWorkManger {
    
    private static var notReachable: String {
        get {
            return "not connected"
        }
    }
    
    // 网络是否连接
    public static var isReachable : Bool {
        return networkReachabilityManager?.isReachable ?? false
    }
    
    public static var isReachableOnCellular: Bool {
        return networkReachabilityManager?.isReachableOnCellular ?? false
    }
    
    public static var isReachableOnEthernetOrWiFi : Bool {
        return networkReachabilityManager?.isReachableOnEthernetOrWiFi ?? false
    }
    
    public class func startListening(onUpdatePerforming listener: @escaping NetworkReachabilityManager.Listener) {
        networkReachabilityManager?.startListening(onUpdatePerforming: listener)
    }
    
    public static var netModel: String { getNetworkType() }
    
    /// 获取网络类型
    private static func getNetworkType() -> String {
        var zeroAddress = sockaddr_storage()
        bzero(&zeroAddress, MemoryLayout<sockaddr_storage>.size)
        zeroAddress.ss_len = __uint8_t(MemoryLayout<sockaddr_storage>.size)
        zeroAddress.ss_family = sa_family_t(AF_INET)
        let defaultRouteReachability = withUnsafePointer(to: &zeroAddress) {
            $0.withMemoryRebound(to: sockaddr.self, capacity: 1) { address in
                SCNetworkReachabilityCreateWithAddress(nil, address)
            }
        }
        guard let defaultRouteReachability = defaultRouteReachability else {
            return notReachable
        }
        var flags = SCNetworkReachabilityFlags()
        let didRetrieveFlags = SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags)
        
        guard didRetrieveFlags == true,
              (flags.contains(.reachable) && !flags.contains(.connectionRequired)) == true
        else {
            return notReachable
        }
        if flags.contains(.connectionRequired) {
            return notReachable
        } else if flags.contains(.isWWAN) {
            return self.cellularType()
        } else {
            return "WiFi"
        }
    }

    /// 获取蜂窝数据类型
    private static func cellularType() -> String {
        let info = CTTelephonyNetworkInfo()
        var status: String
        
        if #available(iOS 12.0, *) {
            guard let statusTemp = info.serviceCurrentRadioAccessTechnology?.values.first else {
                return notReachable
            }
            status = statusTemp
        } else {
            guard let statusTemp = info.currentRadioAccessTechnology else {
                return notReachable
            }
            status = statusTemp
        }
        
        if #available(iOS 14.1, *) {
            if status == CTRadioAccessTechnologyNR || status == CTRadioAccessTechnologyNRNSA {
                return "5G"
            }
        }
        
        switch status {
        case CTRadioAccessTechnologyGPRS,
            CTRadioAccessTechnologyEdge,
        CTRadioAccessTechnologyCDMA1x:
            return "2G"
        case CTRadioAccessTechnologyWCDMA,
            CTRadioAccessTechnologyHSDPA,
            CTRadioAccessTechnologyHSUPA,
            CTRadioAccessTechnologyeHRPD,
            CTRadioAccessTechnologyCDMAEVDORev0,
            CTRadioAccessTechnologyCDMAEVDORevA,
        CTRadioAccessTechnologyCDMAEVDORevB:
            return "3G"
        case CTRadioAccessTechnologyLTE:
            return "4G"
        default:
            return notReachable
        }
    }
}

public enum ResponResult: Int, Codable {
    
    case normal
    case loadIng
    
    case success
    /// 服务器返回异常
    case error
    /// 网络异常
    case failure
}

public struct ResponseData  {
    
    private var moyaResult : Result<Moya.Response, MoyaError>?
    private var _pare : RequestProtocol
    
    public var pare : RequestProtocol { _pare }
    
    public var moyaResponse : Response? {
        successResponse ?? moyaError?.response
    }
    public var successResponse : Response? {
        if case .success(let response) = moyaResult {
            return response
        }
        return nil
    }
    public var moyaError : MoyaError? {
        if case .failure(let error) = moyaResult {
            return error
        }
        return nil
    }
    public func getModel<C:Codable>(errorToast:(view: UIView?, msg:String?)? = nil) -> C? {
        do {
//            print("\(C.self)")
            let model = try successResponse?.map(C.self)
            return model
        } catch {
//            print(json)
//            print(error)
//            print(pare.path)
#if DEBUG
            print(error)
            errorToast?.view?.toast(errorToast?.msg ?? "数据解析错误!")
#endif
            return nil
        }
    }
    
    public var urlStr: String? {
        return url?.relativeString
    }
    
    public var url: URL? {
        return moyaResponse?.request?.url
    }
    
    public var isFromCache:Bool = false
                                    
    public var json: [String : Any]? {
        return try? successResponse?.mapJSON() as? [String : Any]
    }
    
    public var status: ResponseModel<KJSONNull>? {
        if let model: ResponseModel<KJSONNull> = getModel() {
            return model
        }
        return nil
    }
    
    public var statusCode: Int? {
        if case let .underlying(error as NSError, _) = moyaError {
            return error.code
        }
        return status?.code ?? moyaResponse?.statusCode
    }
    
    public var result: ResponResult {
        
        if moyaError != nil || moyaResult == nil || successResponse == nil {
            return .failure
        }
        
        if status?.responseType == .OK {
            return .success
        }
        
        return .error
    }
    
    public var startMilliStamp: CLongLong?
    
    public var netWorkLog: NetWorkLog?
    
    // 获取数据时长 单位毫秒
    public var zmLoadDuration: Int = 0
    
    init(result: Result<Moya.Response, MoyaError>, fromCache: Bool, pare: RequestProtocol, startDate:Date = Date()) {
        moyaResult = result
        isFromCache = fromCache
        _pare = pare
        startMilliStamp = startDate.milliStamp
        zmLoadDuration = Date().millisecondsSince(startDate)
        netWorkLog = NetWorkLog(rData: self)
    }
}

public struct NetWorkLog: Codable {
    
    public var paramsJsonStr,headerJsonStr,jsonStr: String?
    public var path,method,otherDescription:String?
    public var statusCode: Int?
    public var result: ResponResult?
    public var time: TimeInterval? = Date().timeIntervalSince1970
    public var logMsgId: String?
    public var zmLoadDuration: Int?
    public var startMilliStamp: CLongLong?
    public var cURL: String?
        
    public init(rData: ResponseData) {
        let request = rData.moyaResponse?.request
        let response = rData.moyaResponse?.response
        if let body = request?.httpBody {
            if let bodyString = String(data: body, encoding: .utf8) {
                paramsJsonStr = bodyString
            }
        }
        else {
            paramsJsonStr = rData.pare.params?.toJsonString()
        }
        path = rData.pare.path
        jsonStr = rData.json?.toJsonString()
        method = rData.pare.method.rawValue
        headerJsonStr = request?.allHTTPHeaderFields?.toJsonString()
        otherDescription = response?.description ?? rData.moyaError?.errorDescription
        statusCode = rData.statusCode
        result = rData.result
        zmLoadDuration = rData.zmLoadDuration
        startMilliStamp = rData.startMilliStamp
        cURL = rData.moyaResponse?.request?.cURLDescription()
    }
    
    public mutating func update(logMsgId: String?){
        self.logMsgId = logMsgId
    }
    
    public var paramsJson: [String:Any]? { paramsJsonStr?.toDictionary }
    public var jsonData: [String:Any]? { jsonStr?.toDictionary?["data"] as? [String:Any] }
}

public extension URLRequest {
    func cURLDescription() -> String {
        var components = ["curl -v"]

        if let method = self.httpMethod {
            components.append("-X \(method)")
        }

        for (header, value) in self.allHTTPHeaderFields ?? [:] {
            components.append("-H '\(header): \(value)'")
        }

        if let body = self.httpBody,
           let bodyString = String(data: body, encoding: .utf8) {
            components.append("-d '\(bodyString)'")
        }

        if let url = self.url {
            components.append("'\(url.absoluteString)'")
        }

        return components.joined(separator: " \\\n\t")
    }
}
